/**
  ******************************************************************************
  * @file    bsp_rtc.c
  * @author  Iron
  * @date    2018-07-23
  * @version v1.0
  * @brief   bsp rtc c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "stm32f4xx_hal.h"
#include "bsp_rtc.h"

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_RTC_RTOS_ENTER_CRITICAL()  portENTER_CRITICAL()
#define BSP_RTC_RTOS_EXIT_CRITICAL()  portEXIT_CRITICAL()

/* private variables ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/
extern void _Error_Handler( char* file, int line );

/* RTC init function */
void MX_RTC_Init( void )
{
  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;

  memset( &sTime, 0, sizeof( RTC_TimeTypeDef ) );
  memset( &sDate, 0, sizeof( RTC_DateTypeDef ) );

  /**Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  
  if( HAL_RTC_Init( &hrtc ) != HAL_OK ) {
    _Error_Handler( __FILE__, __LINE__ );
  }

  HAL_RTCEx_DeactivateTimeStamp(&hrtc);
  HAL_RTCEx_DeactivateTamper(&hrtc, RTC_TAMPER_1);
  HAL_RTCEx_DeactivateTamper(&hrtc, RTC_TAMPER_2);
  HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
  HAL_RTCEx_DeactivateCoarseCalib(&hrtc);
  HAL_RTCEx_DeactivateCalibrationOutPut(&hrtc);
  HAL_RTCEx_DeactivateRefClock(&hrtc);
 
  /**Initialize RTC and set the Time and Date
  */
  if( HAL_RTCEx_BKUPRead( &hrtc, RTC_BKP_DR0 ) != 0xABCD ) {
    sTime.Hours = 0;
    sTime.Minutes = 0;
    sTime.Seconds = 0;
    sTime.SubSeconds = 0;
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;

    sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
    sDate.Month = RTC_MONTH_JANUARY;
    sDate.Date = 01;
    sDate.Year = 18;

    BSP_RTC_RTOS_ENTER_CRITICAL();

    if( HAL_RTC_SetTime( &hrtc, &sTime, RTC_FORMAT_BIN ) != HAL_OK ) {
      _Error_Handler( __FILE__, __LINE__ );
    }

    if( HAL_RTC_SetDate( &hrtc, &sDate, RTC_FORMAT_BIN ) != HAL_OK ) {
      _Error_Handler( __FILE__, __LINE__ );
    }

    BSP_RTC_RTOS_EXIT_CRITICAL();

    HAL_RTCEx_BKUPWrite( &hrtc, RTC_BKP_DR0, 0xABCD );
  }
}

void HAL_RTC_MspInit( RTC_HandleTypeDef* rtcHandle )
{
  if( rtcHandle->Instance == RTC ) {
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();
  }
}

void HAL_RTC_MspDeInit( RTC_HandleTypeDef* rtcHandle )
{
  if( rtcHandle->Instance == RTC ) {
    /* Peripheral clock disable */
    __HAL_RCC_RTC_DISABLE();
  }
}

void bsp_rtc_init( void )
{
  MX_RTC_Init();
}

time_t bsp_get_rtc_time(struct tm *t, time_t *sec, time_t *us)
{
  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;
  time_t rtc_time;
  struct tm now_tm;

  memset( &now_tm, 0, sizeof( now_tm ) );
  memset( &sTime, 0, sizeof( RTC_TimeTypeDef ) );
  memset( &sDate, 0, sizeof( RTC_DateTypeDef ) );

  BSP_RTC_RTOS_ENTER_CRITICAL();

  HAL_RTC_GetTime( &hrtc, &sTime, RTC_FORMAT_BIN );
  HAL_RTC_GetDate( &hrtc, &sDate, RTC_FORMAT_BIN );

  BSP_RTC_RTOS_EXIT_CRITICAL();

  now_tm.tm_year = sDate.Year + 100;
  now_tm.tm_mon = sDate.Month - 1;
  now_tm.tm_mday = sDate.Date;
  now_tm.tm_hour = sTime.Hours;
  now_tm.tm_min = sTime.Minutes;
  now_tm.tm_sec = sTime.Seconds;

  rtc_time = mktime( &now_tm );

  if(t != NULL) {
    memset( t, 0, sizeof( struct tm ) );
    *t = now_tm;
  }

  if(sec != NULL ) {
    *sec = rtc_time;
  }

  if(us != NULL ) {
    *us = (256 - sTime.SubSeconds) * 1000000 / 256;
  }

  return rtc_time;
}

void bsp_set_rtc_time( time_t sec, time_t us )
{
  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;
  struct tm now_tm;

  memset( &now_tm, 0, sizeof( now_tm ) );
  memset( &sTime, 0, sizeof( RTC_TimeTypeDef ) );
  memset( &sDate, 0, sizeof( RTC_DateTypeDef ) );

  bsp_rtc_gmtime_r( &sec, &now_tm );

  sTime.Hours = now_tm.tm_hour;
  sTime.Minutes = now_tm.tm_min;
  sTime.Seconds = now_tm.tm_sec;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;

  sDate.Year = now_tm.tm_year - 100;
  sDate.Month = now_tm.tm_mon + 1;
  sDate.Date = now_tm.tm_mday;
  sDate.WeekDay = now_tm.tm_wday + 1;

  BSP_RTC_RTOS_ENTER_CRITICAL();

  if ( us < 1000000) {
    uint32_t SSR_value = hrtc.Instance->SSR;
    uint32_t ShiftSubFS = ((1000000 - us) * 256) / 1000000; // max <= 0x7FFF
    if ( ShiftSubFS > SSR_value) {
      HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_RESET, ShiftSubFS - SSR_value);
    } else {
      HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_SET, SSR_value - ShiftSubFS);
    }
  }

  if( HAL_RTC_SetTime( &hrtc, &sTime, RTC_FORMAT_BIN ) == HAL_OK ) {
    HAL_RTC_SetDate( &hrtc, &sDate, RTC_FORMAT_BIN );
  }

  BSP_RTC_RTOS_EXIT_CRITICAL();
}

void bsp_set_rtc_date_time(
  int year, int mon, int mday,
  int hour, int min, int sec, int wday )
{
  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;

  BSP_RTC_RTOS_ENTER_CRITICAL();

  memset( &sTime, 0, sizeof( RTC_TimeTypeDef ) );
  memset( &sDate, 0, sizeof( RTC_DateTypeDef ) );

  sTime.Hours = hour;
  sTime.Minutes = min;
  sTime.Seconds = sec;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;

  sDate.Year = year - 100;
  sDate.Month = mon + 1;
  sDate.Date = mday;
  sDate.WeekDay = wday + 1;

  if( HAL_RTC_SetTime( &hrtc, &sTime, RTC_FORMAT_BIN ) == HAL_OK ) {
    HAL_RTC_SetDate( &hrtc, &sDate, RTC_FORMAT_BIN );
  }

  BSP_RTC_RTOS_EXIT_CRITICAL();
}

/**
  * @breaf gmtime_r / localtime_r
  **/

/* seconds per day */
#define SPD 24*60*60

/* days per month -- nonleap! */
static const short __spm[13] = {
  0,
  (31),
  (31+28),
  (31+28+31),
  (31+28+31+30),
  (31+28+31+30+31),
  (31+28+31+30+31+30),
  (31+28+31+30+31+30+31),
  (31+28+31+30+31+30+31+31),
  (31+28+31+30+31+30+31+31+30),
  (31+28+31+30+31+30+31+31+30+31),
  (31+28+31+30+31+30+31+31+30+31+30),
  (31+28+31+30+31+30+31+31+30+31+30+31),
};

static int __isleap(int year)
{
  /* every fourth year is a leap year except for century years that are
   * not divisible by 400. */
  /*  return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); */
  return (!(year % 4) && ((year % 100) || !(year % 400)));
}

struct tm *bsp_rtc_gmtime_r(const time_t *timep, struct tm *r)
{
  time_t i;
  register time_t work = *timep % (SPD);

  r->tm_sec = work % 60;
  work /= 60;
  r->tm_min = work % 60;
  r->tm_hour = work / 60;
  work = *timep / (SPD);
  r->tm_wday = (4 + work) % 7;
  for (i = 1970;; ++i) {
    register time_t k = __isleap(i) ? 366 : 365;
    if (work >= k)
      work -= k;
    else
      break;
  }
  r->tm_year = i - 1900;
  r->tm_yday = work;

  r->tm_mday = 1;
  if (__isleap(i) && (work > 58)) {
    if (work == 59)
      r->tm_mday = 2; /* 29.2. */
    work -= 1;
  }

  for (i = 11; i && (__spm[i] > work); --i)
    ;
  r->tm_mon = i;
  r->tm_mday += work - __spm[i];
  return r;
}


/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
